/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package pubsub;

import dlcm.Channel;
import dlcm.DLCM;
import dlcm.DLCMControlConstants;
import dlcm.DLCMControlListener;
import dlcm.DLCMListener;
import dlcm.Structure;
import dlcm.StructureListener;
import dlcm.StructureManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import lcmtypes.control_t;

/**
 *
 * @author David McCoy
 */
public class PubSubController implements DLCMListener, DLCMControlListener, StructureListener, PubSubManagerListener{
    String mName;
    DLCM mDLCM;
    //LCM mLCM;
    StructureManager mStructureManager;
    PubSubManager mPubSubManager;
    ArrayList<PubSubViewListener> mPSVListeners;
    long mDSRTimeOut = 500;
    long mRequestTTL = 300000;

    boolean singleStructureManager = true;


    /**
     * Default PubSubController constructor, a name is automatically generated
     * a new DLCM instance is started with a single structure manager
     * NOTE: if mStructureManager
     */
    public PubSubController (){
        //autogenerate name
        Integer RandomNumber = (new Random()).nextInt(1000);
        mName = "PubSub" + RandomNumber.toString();
        mDLCM = new DLCM();
        setupController();


    }
    public PubSubController (String name, StructureManager structureManager, String provider){
        mName = name;
        if (structureManager != null){
            singleStructureManager = false;
            mStructureManager = structureManager;
        }
        try {
            mDLCM = new DLCM(mStructureManager, provider, this);
        }    
        catch (Exception ex){
            this.PSCError("Error creating DLCM: " + ex.getMessage());
        }
        setupController();
    }
    private void setupController(){
        mPSVListeners = new ArrayList<PubSubViewListener>();
        mDLCM.addDLCMListener(this);
        mStructureManager = mDLCM.getStructureManager();
        mPubSubManager = new PubSubManager(mDLCM, mStructureManager);
        mPubSubManager.addPubSubManagerListener(this);
        mPubSubManager.AddPubSub(mName, mStructureManager.getBoardID());
        mStructureManager.addStructureListener(this);
    }
    public void addPubSubViewListener(PubSubViewListener psvl){
        mPSVListeners.add(psvl);
    }
    public void removePubSubViewListener(PubSubViewListener psvl){
        mPSVListeners.remove(psvl);
    }
    public void close(){
        mDLCM.close();
    }

    // <editor-fold defaultstate="collapsed" desc="Getters/Setters">
    public PubSubManager getPubSubManager(){
        return mPubSubManager;
    }
    public void setDelayedStructureRequestTimeOut(long timeOut){
        mDSRTimeOut = timeOut;
    }
    public long getDelayedStructureRequestTimeOut(){
        return mDSRTimeOut;
    }
    public void setRequestStructuresTTL(long timeOut){
        mRequestTTL = timeOut;
    }
    public long getRequestStructuresTTL(){
        return mRequestTTL;
    }
    public boolean startLCM(String provider){
        //LCM lcm = null;
        try {
            mDLCM.startLCM(provider, this);
            //mLCM = new LCM(provider);
            //setupCommunication();
            
            //mLCM = new LCM(provider);
        } catch(Exception ex){
            PSCError("Error Creating LCM " + ex.getMessage());
            return false;
        }
        return true;
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Flags">
    boolean isSingleStructureManager(){
        return singleStructureManager;
    }
    boolean isConnected(){
        //return (mLCM != null);
        return mDLCM.isConnected();
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Chat">
    public void sendChatString(String chatMessage){
        control_t controlOut = new control_t();
        controlOut.from = mName;
        controlOut.control_string = chatMessage;
        controlOut.board_id = mStructureManager.getBoardID();
        controlOut.flags = 0;
        controlOut.status = 0;
        publishControl("chat", controlOut);
        
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Publish Functions">
    private void publishControl(String channel, control_t controlOut){
        try{
            mDLCM.publish(channel, controlOut);
            //mLCM.publish(channel, controlOut);
        }
        catch (Exception ex){
            PSCError(ex.getMessage());
        }
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Control Requests">
    private void pingRequest(control_t msg){
        PSCDebug("Received a Ping... Responding... Pong!");
        control_t control = new control_t();
        control.control_string = "Pong";
        control.from = mName;
        control.flags = 0;
        control.status = DLCMControlConstants.PING;
        control.board_id = mStructureManager.getBoardID();
        
        //Add this to my Remote Map
        if (!mPubSubManager.contains(msg.from)){
            mPubSubManager.AddPubSub(msg.from, msg.board_id);
        }
        publishControl(msg.from + ".control", control);
    }
    private void structureRequest(){
        PSCDebug("Received a request for structures");
        try{
            control_t control = new control_t();
            control.from = mName;
            control.control_string = mStructureManager.getLCMString();
            control.flags = 0;
            control.status = DLCMControlConstants.STRUCTURES;
            control.board_id = mStructureManager.getBoardID();
            publishControl("control", control);
        }
        catch (IOException ex){
            PSCError("Error generating LCM structure string: " + ex.getMessage());
        }
    }
    private void channelRequest(control_t msg){
        PSCDebug("Received a request for instances");
        control_t control = new control_t();
        control.control_string = mPubSubManager.getPubSub(mName).GenerateRawChannelString();
        control.from = mName;
        control.flags = 0;
        control.status = DLCMControlConstants.CHANNELS;
        control.board_id = mStructureManager.getBoardID();
        publishControl(msg.from + ".control", control);
    }
    private void channelResolveRequest(control_t msg){
        //this could have caused some heaches
        if (msg.from.equals(mName)){
            return;
        }
        //if we don't have it, jsut end
        if (!mDLCM.containsChannel(msg.control_string)){
            return;
        }
        //we have the same name, respond back
        control_t control = new control_t();
        control.control_string = msg.control_string;
        control.from = mName;
        control.flags = 0;
        control.status = DLCMControlConstants.CHANNEL_CHECK;
        control.board_id = mStructureManager.getBoardID();
        publishControl(msg.from + ".control", control);
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Control Commands">
    public void channelResolveQuery (String channel){
        PSCDebug("Sending a request to all channels for a name resolution");
        control_t control = new control_t();
        control.control_string = channel;
        control.from = mName;
        control.flags = DLCMControlConstants.CHANNEL_CHECK;
        control.status = 0;
        control.board_id = mStructureManager.getBoardID();
        publishControl("control", control);
    }
    public void pingQuery(){
        PSCDebug("Sending Ping Request");
        control_t control = new control_t();
        control.control_string = "Ping";
        control.from = mName;
        control.flags = DLCMControlConstants.PING;
        control.status = 0;
        control.board_id = mStructureManager.getBoardID();
        publishControl("control", control);
    }
    public void channelRequest(String pubSubName){
        if (mName.matches(pubSubName)){
            return;
        }
        control_t control = new control_t();
        control.control_string = "";
        control.from = mName;
        control.flags = DLCMControlConstants.CHANNELS;
        control.status = 0;
        control.board_id = mStructureManager.getBoardID();
        publishControl(pubSubName + ".control", control);
    }
    public void channelRequest(){
        //go through each of the entries in the pubsub list
        ArrayList<String> pubSubNames = new ArrayList<String>(mPubSubManager.getPubSubNames());
        if (pubSubNames.contains(mName)){
            pubSubNames.remove(mName);
        }

        control_t control = new control_t();
        control.control_string = "";
        control.from = mName;
        control.flags = DLCMControlConstants.CHANNELS;
        control.status = 0;
        control.board_id = mStructureManager.getBoardID();
        for (String name : pubSubNames){
            publishControl(name + ".control", control);
        }
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Control Response">
    private void pingResponse (control_t msg){
        //add this to my remote map
        PSCDebug("Got a Pong from a Ping");
        if (!mPubSubManager.contains(msg.from)){
            mPubSubManager.AddPubSub(msg.from, msg.board_id);
        }
    }
    private void channelResponse(control_t msg){
        boolean needStructures = false;
        if (!mPubSubManager.contains(msg.from)){
            mPubSubManager.AddPubSub(msg.from, msg.board_id);
        }
        try {
            mPubSubManager.getPubSub(msg.from).ParseRawInputString(msg.control_string);
        }
        catch (Exception ex){
            needStructures = true;
            PSCImportant("DLCM Instance Parse Notification: " + ex.getMessage());
        }
        finally {
            if (needStructures){
                PSCInfo("Scheduling a request for structures to: " + msg.from);
                DelayedStructureRequest dsr = new DelayedStructureRequest();
                dsr.setDestination(msg.from);
                dsr.setFrom(mName);
                Timer srt = new Timer();
                srt.schedule(dsr, mDSRTimeOut);

                mPubSubManager.getPubSub(msg.from).AddUnparsedString(msg.control_string);
                ChannelTimerToLive cttl = new ChannelTimerToLive();
                cttl.setControlValue(msg.from);
                Timer timer = new Timer();
                timer.schedule(cttl, mRequestTTL);
            }
        }
    }
    private void structureResponse (control_t msg){
        mDLCM.parseStructure(msg.control_string);
        //We have a set of control structurs that require a resposne
        //go through the list of instance control resposnes I've been holding on
        for (String name : mPubSubManager.getPubSubNames()){
            PubSub ps = mPubSubManager.getPubSub(name);
            if (ps.isSameBoard(msg.board_id)){
                ArrayList<String> uis = ps.getUnparsedChannelStrings();
                Iterator itr = uis.iterator();
                while (itr.hasNext()){
                    String channelString = (String) itr.next();
                    try {
                        ps.ParseRawInputString(channelString);
                        itr.remove();
                    }
                    catch(Exception ex){
                        PSCError("Iteration error for the parser: " + ex.getMessage());
                    }
                }
            }
        }
    }
    private void channelResolveResponse(control_t msg){
        String newName = null;
        if (mDLCM.containsChannel(msg.control_string)){
            newName = mDLCM.fixChannelName(msg.control_string);
        }
        channelResolveQuery(newName);
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="LCM Listener">    
    public void DLCMControlMessageReceived(String channel, control_t msg) {
        //String [] filters = null;
        try{
            //filters = channel.split("\\.");
            if (channel.matches("chat")){
//                control_t msg = new control_t(control);
                if (msg.from.equals(mName)){
                    return;
                }
                PSCInfo("From: " + msg.from + ": " + msg.control_string);
            }
            if ((channel.equals(mName + ".control")) || (channel.equals("control"))){
//                control_t msg = new control_t(ins);
                //Don't read control message from self
                if ((channel.equals("control")) && msg.from.equals(mName)){
                    return;
                }
                //Send out messages, when something happens
                PSCDebug("From: " + msg.from + "Message: " + msg.control_string);
                
                //Received a Response
                if (msg.status == DLCMControlConstants.STRUCTURES){
                    structureResponse(msg);
                }
                else if (msg.status == DLCMControlConstants.CHANNELS){
                    channelResponse(msg);
                }
                else if (msg.status == DLCMControlConstants.PING){
                    pingResponse(msg);
                }
                else if (msg.status == DLCMControlConstants.CHANNEL_CHECK){
                    channelResolveResponse(msg);
                }
                
                //Received a Request
                if (msg.flags == DLCMControlConstants.STRUCTURES){
                    //send the structure data
                    structureRequest();
                }
                else if (msg.flags == DLCMControlConstants.CHANNELS){
                    //send channel data
                    channelRequest(msg);
                }
                else if (msg.flags == DLCMControlConstants.PING){
                    pingRequest(msg);
                }
                else if (msg.flags == DLCMControlConstants.CHANNEL_CHECK){
                    channelResolveRequest(msg);
                }
                
            }
        }
        catch (Exception ex){
            PSCError(ex.getMessage());
            return;
        }

    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="DLCM Listener">    
    public void DLCMChannelAdded(String channelName) {
        PSCImportant("DLCM Channel Added: " + channelName);
        controllerChanged();
    }
    public void DLCMChannelRemoved(String channelName) {
        PSCImportant("DLCM Channel Removed: " + channelName);
        controllerChanged();
    }
    public void DLCMMessageReceived(String channelName, Structure structure) {

    }
    public void DLCMError(String error, String channel) {
        PSCError("DLCM Error: " + error + " on channel " + channel);
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Logger">
    public void PSCFatal(String message){
        for (PubSubViewListener psvl : mPSVListeners){
            psvl.pscFatal("Fatal: " + message);
        }
    }
    public void PSCError(String message){
        for (PubSubViewListener psvl : mPSVListeners){
            psvl.pscError("Error: " + message);
        }
    }
    public void PSCWarning(String message){
        for (PubSubViewListener psvl : mPSVListeners){
            psvl.pscWarning("Warning: " + message);
        }
    }
    public void PSCImportant(String message){
        for (PubSubViewListener psvl : mPSVListeners){
            psvl.pscImportant("Important: " + message);
        }
    }
    public void PSCInfo(String message){
        for (PubSubViewListener psvl : mPSVListeners){
            psvl.pscInfo("Info: " + message);
        }
    }
    public void PSCDebug(String message){
        for (PubSubViewListener psvl : mPSVListeners){
            psvl.pscDebug("Debug: " + message);
        }
    }
    public void PSCVerbose(String message){
        for (PubSubViewListener psvl : mPSVListeners){
            psvl.pscVerbose("Verbose: " + message);
        }
    }
    private void controllerChanged(){
        for (PubSubViewListener psvl : mPSVListeners){
            psvl.pscChanged();
        }
    }
    //</editor-fold>
    // <editor-fold defaultstate="collapsed" desc="Structure Listener">
    public void structureChanging() {

    }

    public void structureChanged() {
        mPubSubManager.StructuresChanged();
    }

    public void pubsubAdded(String pubsubName) {
        controllerChanged();
    }

    public void pubsubRemoved(String pubsubName) {
        controllerChanged();
    }
    //</editor-fold>
    public class ChannelTimerToLive extends TimerTask {

        private String Name;

        @Override
        public void run() {
            if (!mPubSubManager.getPubSub(Name).getUnparsedChannelStrings().isEmpty()){
                mPubSubManager.getPubSub(Name).ClearUnparsedStrings();
                PSCError("Unparsed Instance strings from " + Name + " have timed out, and will be removed");
            }
        }
        public void setControlValue(String name){
            Name = new String (name);
        }

    }
    public class DelayedStructureRequest extends TimerTask {

        public String Name;
        public String From;

        @Override
        public void run() {
            PSCInfo("Requesting Structures from " + Name);
            control_t control = new control_t();
            control.control_string = "";
            control.board_id = mStructureManager.getBoardID();
            control.from = From;
            control.flags = DLCMControlConstants.STRUCTURES;
            control.status = 0;
            publishControl(Name + ".control", control);
        }
        public void setDestination(String name){
            Name = name;
        }
        public void setFrom(String from){
            From = from;
        }

    }

    // <editor-fold defaultstate="collapsed" desc="PubSubManager functions">
    public void generateNewChannel(String channelName, String structureName){
        mDLCM.generateNewChannel(channelName, structureName);
        mPubSubManager.getPubSub(mName).AddChannelName(channelName);
    }
    public Channel getChannel(String channelName){
        return mDLCM.getChannel(channelName);
    }

    public Object[] getPubSubNamesListData(){
        return mPubSubManager.getPubSubNamesListData();
    }
    public Object[] getChannelsListData(String pubSubName){
        return mPubSubManager.getChannelsListData(pubSubName);
    }
    public Object[] getMembersListData (String pubSubName, String channelName){
        return mPubSubManager.getMembersListData(pubSubName, channelName);
    }
    public Object[] getWidgetsListData (String pubSubName, String channelName, String memberName) throws Exception {
        return mPubSubManager.getWidgetsListData(pubSubName, channelName, memberName);
    }

   public void addOutputWidget(String pubSub, String channelName, String member, String widgetName) throws Exception{
       mPubSubManager.addOutputWidget(pubSub, channelName, member, widgetName);
   }
   public void removeOutputWidget (String channelName){
       mPubSubManager.removeOutputWidget(channelName);
   }
   public void addInputWidget (String pubSub, String channelName, String member, String widgetName) throws Exception{
       mPubSubManager.addInputWidget(pubSub, channelName, member, widgetName);
   }
   public void removeInputWidget (String channelName){
       mPubSubManager.removeInputWidget(channelName);
   }
   public void addInputOutputWidget (String pubSub, String channelName, String member, String widgetName) throws Exception{
       mPubSubManager.addInputOutputWidget(pubSub, channelName, member, widgetName);
   }
   public void removeInputOutputWidget (String channelName){
       mPubSubManager.removeInputOutputWidget(channelName);
   }
    // </editor-fold>


}
